/*
 * We don't use unput, so don't generate code for it.
 */
%option nounput

/*
 * We don't read from the terminal.
 */
%option never-interactive

/*
 * The language we're scanning is case-insensitive.
 */
%option caseless

/*
 * Prefix scanner routines with "Radius" rather than "yy", so this scanner
 * can coexist with other scanners.
 */
%option prefix="Radius"

%option outfile="radius_dict.c"

%{
	/* radius_dict.l
	*
	* RADIUS dictionary parser
	*
	* $Id: radius_dict.l 37984 2011-07-11 23:13:44Z gerald $
	*
	* Wireshark - Network traffic analyzer
	* By Gerald Combs <gerald@wireshark.org>
	* Copyright 1998 Gerald Combs
	*
	* This program is free software; you can redistribute it and/or
	* modify it under the terms of the GNU General Public License
	* as published by the Free Software Foundation; either version 2
	* of the License, or (at your option) any later version.
	*
	* This program is distributed in the hope that it will be useful,
	* but WITHOUT ANY WARRANTY; without even the implied warranty of
	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	* GNU General Public License for more details.
	*
	* You should have received a copy of the GNU General Public License
	* along with this program; if not, write to the Free Software
	* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
	*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <epan/packet.h>
#include <epan/dissectors/packet-radius.h>
#include "radius_dict_lex.h"
#include <wsutil/file_util.h>

#ifdef _WIN32
/* disable Windows VC compiler warning "signed/unsigned mismatch" associated  */
/* with YY_INPUT code generated by flex versions such as 2.5.35.              */
#pragma warning (disable:4018)
#endif

#define ECHO
#define MAX_INCLUDE_DEPTH 10

	void add_vendor(const gchar* name, guint32 vendor_id, guint vendor_type_octets, guint vendor_length_octets, gboolean vendor_has_flags);
	void add_value(const gchar* attrib_name,const  gchar* value_repr, long value);
	void add_tlv(const gchar* name, const  gchar* code, radius_attr_dissector_t type, const gchar* current_attr);
	void add_attribute(const gchar*,const  gchar*, radius_attr_dissector_t,const  gchar*, gboolean, gboolean, const gchar*);

	static YY_BUFFER_STATE include_stack[10];
	static int include_stack_ptr = 0;

	static radius_dictionary_t* dict = NULL;
	static GHashTable* value_strings = NULL; /* GArray(value_string) by attribute name */

	static gchar* attr_name = NULL;
	static gchar* attr_id = NULL;
	static radius_attr_dissector_t* attr_type = NULL;
	static gchar* attr_vendor = NULL;
	static gchar* vendor_name = NULL;
	static guint32 vendor_id = 0;
	static guint vendor_type_octets = 1;
	static guint vendor_length_octets = 1;
	static gboolean vendor_has_flags = FALSE;
	static gchar* value_repr = NULL;
	static gboolean encrypted = FALSE;
	static gboolean has_tag = FALSE;
	static gchar* current_vendor = NULL;
	static gchar* current_attr = NULL;

	static GString* error = NULL;
	static gchar* directory = NULL;
	static int linenums[] = {1,1,1,1,1,1,1,1,1,1};
	static gchar* fullpaths[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};

%}

%START WS_OUT VENDOR VENDOR_W_NAME ATTR ATTR_W_NAME ATTR_W_ID ATTR_W_TYPE ATTR_W_VENDOR VALUE VALUE_W_ATTR VALUE_W_NAME INCLUDE JUNK BEGIN_VENDOR END_VENDOR VENDOR_W_ID VENDOR_W_FORMAT VENDOR_W_TYPE_OCTETS VENDOR_W_LENGTH_OCTETS VENDOR_W_CONTINUATION BEGIN_TLV END_TLV
%%
[:blank:]   ;
#[^\n]*		;

<JUNK>.*\qn		;

<WS_OUT>VENDOR { BEGIN VENDOR; }
<WS_OUT>ATTRIBUTE { BEGIN ATTR; }
<WS_OUT>VALUE { BEGIN VALUE; }
<WS_OUT>\$INCLUDE { BEGIN INCLUDE; }
<WS_OUT>BEGIN-VENDOR { BEGIN BEGIN_VENDOR; }
<WS_OUT>END-VENDOR { BEGIN END_VENDOR; }
<WS_OUT>BEGIN-TLV { BEGIN BEGIN_TLV; }
<WS_OUT>END-TLV { BEGIN END_TLV; }

<BEGIN_VENDOR>[0-9a-z_-]+ {
    if (current_vendor) {
        g_free(current_vendor);
    }
    current_vendor = g_strdup(yytext);
    BEGIN WS_OUT;
}
<END_VENDOR>[^\n]* {
    if (current_vendor) {
        g_free(current_vendor);
        current_vendor = NULL;
    }
    BEGIN WS_OUT;
}

<BEGIN_TLV>[0-9a-z_-]+ {
    if (current_attr) {
        g_free(current_attr);
    }
    current_attr = g_strdup(yytext);
    BEGIN WS_OUT;
}
<END_TLV>[^\n]* {
    if (current_attr) {
        g_free(current_attr);
        current_attr = NULL;
    }
    BEGIN WS_OUT;
}

<VENDOR>[0-9a-z_-]+   {
    vendor_name = g_strdup(yytext);
    vendor_type_octets = 1;
    vendor_length_octets = 1;
    vendor_has_flags = FALSE;
    BEGIN VENDOR_W_NAME;
}
<VENDOR_W_NAME>[0-9]+   {
    vendor_id = strtol(yytext,NULL,10);
    BEGIN VENDOR_W_ID;
}
<VENDOR_W_NAME>0x[0-9a-f]+   {
    vendor_id = strtol(yytext,NULL,16);
    BEGIN VENDOR_W_ID;
}
<VENDOR_W_ID>format= {
    BEGIN VENDOR_W_FORMAT;
}
<VENDOR_W_FORMAT>[124] {
    vendor_type_octets = strtol(yytext,NULL,10);
    BEGIN VENDOR_W_TYPE_OCTETS;
}
<VENDOR_W_TYPE_OCTETS>,[012] {
    vendor_length_octets = strtol(yytext+1,NULL,10);
    BEGIN VENDOR_W_LENGTH_OCTETS;
}
<VENDOR_W_LENGTH_OCTETS>,c {
    vendor_has_flags = TRUE;
    BEGIN VENDOR_W_CONTINUATION;
}
<VENDOR_W_FORMAT>\n |
<VENDOR_W_TYPE_OCTETS>\n |
<VENDOR_W_LENGTH_OCTETS>\n |
<VENDOR_W_CONTINUATION>\n |
<VENDOR_W_ID>\n {
    add_vendor(vendor_name, vendor_id, vendor_type_octets, vendor_length_octets, vendor_has_flags);
    g_free(vendor_name);
    BEGIN WS_OUT;
}

<ATTR>[0-9a-z_/-]+			{ attr_name = g_strdup(yytext); encrypted = FALSE; has_tag = FALSE; BEGIN ATTR_W_NAME; }
<ATTR_W_NAME>[0-9]+			{ attr_id = g_strdup(yytext);  BEGIN ATTR_W_ID;}
<ATTR_W_NAME>0x[0-9a-f]+		{ attr_id = g_strdup_printf("%u",(int)strtoul(yytext,NULL,16)); BEGIN ATTR_W_ID;}
<ATTR_W_ID>integer			{ attr_type = radius_integer;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>string			{ attr_type = radius_string;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>octets			{ attr_type = radius_octets;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>ipaddr			{ attr_type = radius_ipaddr;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>ipv6addr			{ attr_type = radius_ipv6addr;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>ipv6prefix			{ attr_type = radius_ipv6prefix;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>ipxnet			{ attr_type = radius_ipxnet;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>date				{ attr_type = radius_date;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>abinary			{ attr_type = radius_abinary;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>ether  			{ attr_type = radius_ether;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>ifid				{ attr_type = radius_ifid;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>byte				{ attr_type = radius_integer;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>short			{ attr_type = radius_integer;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>signed			{ attr_type = radius_signed;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>combo-ip			{ attr_type = radius_combo_ip;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>tlv				{ attr_type = radius_tlv;  BEGIN ATTR_W_TYPE; }
<ATTR_W_ID>[0-9a-z_-]+			{ attr_type = radius_octets;  BEGIN ATTR_W_TYPE; }
<ATTR_W_TYPE>has_tag[,]?		{ has_tag = TRUE; }
<ATTR_W_TYPE>encrypt=1[,]?		{ encrypted=TRUE; }
<ATTR_W_TYPE>[0-9a-z_-]+=([^\n]*)	;
<ATTR_W_TYPE>[0-9a-z_-]+		{
    attr_vendor = g_strdup(yytext);
    add_attribute(attr_name,attr_id,attr_type,attr_vendor,encrypted,has_tag,current_attr);
    g_free(attr_id);
    g_free(attr_vendor);
    g_free(attr_name);
    attr_id = NULL;
    attr_vendor = NULL;
    attr_name = NULL;
    BEGIN WS_OUT;
}
<ATTR_W_TYPE>\n						{
    add_attribute(attr_name,attr_id,attr_type,current_vendor,encrypted,has_tag,current_attr);
    g_free(attr_id);
    g_free(attr_name);
    linenums[include_stack_ptr]++;
    has_tag = FALSE;
    encrypted=FALSE;
    BEGIN WS_OUT;
}
<ATTR_W_VENDOR>\n					{
    add_attribute(attr_name,attr_id,attr_type,attr_vendor,encrypted,has_tag,current_attr);
    g_free(attr_id);
    g_free(attr_vendor);
    g_free(attr_name);
    linenums[include_stack_ptr]++;
    BEGIN WS_OUT;
};

<VALUE>[0-9a-z_/-]+					{ attr_name = g_strdup(yytext); BEGIN VALUE_W_ATTR; }
<VALUE_W_ATTR>[^[:blank:]]+			{ value_repr = g_strdup(yytext); BEGIN VALUE_W_NAME; }
<VALUE_W_NAME>[0-9]+				{ add_value(attr_name,value_repr,strtol(yytext,NULL,10));  g_free(attr_name); g_free(value_repr); BEGIN WS_OUT;}
<VALUE_W_NAME>0x[0-9a-f]+			{ add_value(attr_name,value_repr,strtol(yytext,NULL,16));  g_free(attr_name); g_free(value_repr); BEGIN WS_OUT;}

<INCLUDE>[^[:blank:]\n]+   {
	if ( include_stack_ptr >= MAX_INCLUDE_DEPTH ) {
		g_string_append_printf(error, "$INCLUDE files nested to deeply\n");
		yyterminate();
	}

	include_stack[include_stack_ptr++] = YY_CURRENT_BUFFER;

	fullpaths[include_stack_ptr] = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s",
	    directory,yytext);

	yyin = ws_fopen( fullpaths[include_stack_ptr], "r" );

	if (!yyin) {
		if (errno) {
			g_string_append_printf(error, "Could not open file: '%s', error: %s\n", fullpaths[include_stack_ptr], g_strerror(errno) );
			yyterminate();
		}
	} else {
		linenums[include_stack_ptr] = 1;
		yy_switch_to_buffer(yy_create_buffer( yyin, YY_BUF_SIZE ) );
	}


	BEGIN WS_OUT;
}

<<EOF>> {

	fclose(yyin);
	yyin = NULL;

	if ( --include_stack_ptr < 0 ) {
		yyterminate();
	} else {
		g_free(fullpaths[include_stack_ptr+1]);
		fullpaths[include_stack_ptr+1] = NULL;

		yy_delete_buffer( YY_CURRENT_BUFFER );
		yy_switch_to_buffer(include_stack[include_stack_ptr]);
	}

	BEGIN WS_OUT;
}

\n 	{ linenums[include_stack_ptr]++; BEGIN WS_OUT; }


%%

void add_vendor(const gchar* name, guint32 vendor_id, guint vendor_type_octets, guint vendor_length_octets, gboolean vendor_has_flags) {
	radius_vendor_info_t* v;

	v = g_hash_table_lookup(dict->vendors_by_id, GUINT_TO_POINTER(vendor_id));

	if (!v) {
		v = g_malloc(sizeof(radius_vendor_info_t));
		v->attrs_by_id = g_hash_table_new(g_direct_hash,g_direct_equal);
		v->code = vendor_id;
		v->ett = -1;
		v->name = NULL;
	}
        /* Assume that the dictionary knows the 'ground truth' about the 
         * type/length/has_flags information and thus allow the dictionary to
         * overwrite these values even for vendors that have already been loaded.
         */
	v->type_octets = vendor_type_octets;
	v->length_octets = vendor_length_octets;
	v->has_flags = vendor_has_flags; 

	if (v->name)
		g_free((gpointer) v->name);
	v->name = g_strdup(name);

	g_hash_table_insert(dict->vendors_by_id,GUINT_TO_POINTER(v->code),v);
	g_hash_table_insert(dict->vendors_by_name, (gpointer) v->name, v);
}

void add_attribute(const gchar* name, const  gchar* codestr, radius_attr_dissector_t type, const  gchar* vendor_name, gboolean crypt, gboolean tagged, const gchar* current_attr) {
	radius_attr_info_t* a;
	GHashTable* by_id;
	guint32 code;


	if (current_attr){
		add_tlv(name, codestr, type, current_attr);
		return;
	}


	if (vendor_name) {
		radius_vendor_info_t* v;
		v = g_hash_table_lookup(dict->vendors_by_name,vendor_name);

		if (! v) {
			g_string_append_printf(error, "Vendor: '%s', does not exist in %s:%i \n", vendor_name, fullpaths[include_stack_ptr], linenums[include_stack_ptr] );
			BEGIN JUNK;
			return;
		} else {
			by_id = v->attrs_by_id;
		}
	} else {
		by_id = dict->attrs_by_id;
	}

	code=strtol(codestr, NULL, 10);

	a=g_hash_table_lookup(by_id, GUINT_TO_POINTER(code));

	if (!a) {
		a = g_malloc(sizeof(radius_attr_info_t));
		a->name = NULL;
		a->dissector = NULL;
	}

	a->code = code;
	a->encrypt = crypt;
	a->tagged =  tagged;
	a->type = type;
	a->vs = NULL;
	a->hf = -1;
	a->hf64 = -1;
	a->hf_tag = -1;
	a->hf_len = -1;
	a->ett = -1;
	a->tlvs_by_id = NULL;

	if (a->name)
		g_free((gpointer) a->name);
	a->name = g_strdup(name);

	g_hash_table_insert(by_id, GUINT_TO_POINTER(code),a);
	g_hash_table_insert(dict->attrs_by_name,(gpointer) (a->name),a);
}

void add_tlv(const gchar* name, const  gchar* codestr, radius_attr_dissector_t type, const gchar* current_attr) {
	radius_attr_info_t* a;
	radius_attr_info_t* s;
	guint32 code;

	a = g_hash_table_lookup(dict->attrs_by_name, current_attr);

	if (! a) {
		g_string_sprintfa(error, "Attr: '%s', does not exist in %s:%i \n", current_attr, fullpaths[include_stack_ptr], linenums[include_stack_ptr]);
		BEGIN JUNK;
		return;
	}

	if (type == radius_tlv) {
		g_string_sprintfa(error, "sub-TLV: '%s', sub-TLV's type is specified as tlv in %s:%i \n", name, fullpaths[include_stack_ptr], linenums[include_stack_ptr]);
		BEGIN JUNK;
		return;
	}


	if (! a->tlvs_by_id) {
		a->tlvs_by_id = g_hash_table_new(g_direct_hash,g_direct_equal);
	}

	code=strtol(codestr, NULL, 10);
		
	s = g_hash_table_lookup(a->tlvs_by_id, GUINT_TO_POINTER(code));

	if (!s) {
		s = g_malloc(sizeof(radius_attr_info_t));
		s->name = NULL;
		s->dissector = NULL;
	}

	s->code = code;
	s->type = type;
	s->encrypt = FALSE;
	s->tagged = FALSE;
	s->dissector = NULL;
	s->vs = NULL;
	s->hf = -1;
	s->hf64 = -1;
	s->hf_tag = -1;
	s->hf_len = -1;
	s->ett = -1;
	s->tlvs_by_id = NULL;

	if (s->name)
		g_free((gpointer) s->name);
	s->name = g_strdup(name);

	g_hash_table_insert(a->tlvs_by_id,GUINT_TO_POINTER(s->code),s);
	g_hash_table_insert(dict->tlvs_by_name,(gpointer) (s->name),s);
}

void add_value(const gchar* attrib_name, const gchar* value_repr, long value) {
	value_string v;
	GArray* a = g_hash_table_lookup(value_strings,attrib_name);

	if (! a) {
		a = g_array_new(TRUE,TRUE,sizeof(value_string));
		g_hash_table_insert(value_strings,g_strdup(attrib_name),a);
	}

	v.value = value;
	v.strptr = g_strdup(value_repr);

	g_array_append_val(a,v);
}

static void setup_tlvs(gpointer k _U_, gpointer v, gpointer p _U_) {
	radius_attr_info_t* s = v;
	gpointer key;

	union {
		GArray* a;
		gpointer p;
	} vs;

	if (g_hash_table_lookup_extended(value_strings, s->name, &key, &vs.p)) {
		s->vs = (value_string*)(void *)vs.a->data;
		g_array_free(vs.a, FALSE);
		g_hash_table_remove(value_strings, key);
		g_free(key);
	}
}

static void setup_attrs(gpointer k _U_, gpointer v, gpointer p _U_) {
	radius_attr_info_t* a = v;
	gpointer key;

	union {
		GArray* a;
		gpointer p;
	} vs;

	if (g_hash_table_lookup_extended(value_strings,a->name,&key,&vs.p) ) {
		a->vs = (value_string*)(void *)vs.a->data;
		g_array_free(vs.a,FALSE);
		g_hash_table_remove(value_strings,key);
		g_free(key);
	}

	if (a->tlvs_by_id) {
		g_hash_table_foreach(a->tlvs_by_id, setup_tlvs, p);
	}
}

static void setup_vendors(gpointer k _U_, gpointer v, gpointer p) {
	radius_vendor_info_t* vnd = v;

	g_hash_table_foreach(vnd->attrs_by_id,setup_attrs,p);
}

static gboolean destroy_value_strings(gpointer k, gpointer v, gpointer p _U_) {
	value_string* vs = (value_string*)(void *)(((GArray*)v)->data);

	g_free(k);

	for (;vs->strptr;vs++) {
		g_free((void*)vs->strptr);
	}

	g_array_free(v,TRUE);
	return TRUE;
}

static gboolean destroy_tlvs(gpointer k _U_, gpointer v, gpointer p _U_) {
	radius_attr_info_t* s = v;
	int i;

	g_free((gpointer) (s->name));

	if (s->vs) {
		for(i=0; s->vs[i].strptr; i++) {
			g_free((void *)s->vs[i].strptr);
		}
		g_free((void *)s->vs);
	}
	g_free(s);
	return TRUE;
}

static gboolean destroy_attrs(gpointer k _U_, gpointer v, gpointer p _U_) {
	radius_attr_info_t* a = v;
	int i;

	g_free((gpointer) (a->name));
	if (a->tlvs_by_id) {
		g_hash_table_foreach_remove(a->tlvs_by_id, destroy_tlvs, p);
		g_hash_table_destroy(a->tlvs_by_id);
	}

	if (a->vs) {
		for(i=0; a->vs[i].strptr; i++) {
			g_free((void *)a->vs[i].strptr);
		}
		g_free((void *)a->vs);
	}
	g_free(a);
	return TRUE;
}

static gboolean destroy_vendors(gpointer k _U_, gpointer v, gpointer p) {
	radius_vendor_info_t* vnd = v;
	g_free( (gpointer) vnd->name);
	g_hash_table_foreach_remove(vnd->attrs_by_id,destroy_attrs,p);
	g_hash_table_destroy(vnd->attrs_by_id);
	g_free(vnd);
	return TRUE;
}

static void destroy_dict(radius_dictionary_t* d) {
	g_hash_table_foreach_remove(d->attrs_by_id,destroy_attrs,NULL);
	g_hash_table_foreach_remove(d->vendors_by_id,destroy_vendors,NULL);
	g_hash_table_destroy(d->vendors_by_id);
	g_hash_table_destroy(d->attrs_by_id);
	g_hash_table_destroy(d->vendors_by_name);
	g_hash_table_destroy(d->attrs_by_name);
	g_free(d);
}

gboolean radius_load_dictionary (radius_dictionary_t* d, gchar* dir, const gchar* filename, gchar** err_str) {
	int i;

	dict = d;
	directory = dir;

	fullpaths[include_stack_ptr] = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s",
	    directory,filename);

	error = g_string_new("");

	yyin = ws_fopen(fullpaths[include_stack_ptr],"r");

	if (!yyin) {
		g_string_append_printf(error, "Could not open file: '%s', error: %s\n", fullpaths[include_stack_ptr], g_strerror(errno) );
		g_free(fullpaths[include_stack_ptr]);
		*err_str = error->str;
		g_string_free(error,FALSE);
		return FALSE;
	}

	value_strings = g_hash_table_new(g_str_hash,g_str_equal);

	BEGIN WS_OUT;

	yylex();

	if (yyin != NULL) fclose(yyin);
	yyin = NULL;

	for (i=0; i < 10; i++) {
		if (fullpaths[i]) g_free(fullpaths[i]);
	}

	g_hash_table_foreach(dict->attrs_by_id,setup_attrs,NULL);
	g_hash_table_foreach(dict->vendors_by_id,setup_vendors,NULL);
	g_hash_table_foreach_remove(value_strings,destroy_value_strings,NULL);

	if (error->len > 0) {
		 *err_str = error->str;
		g_string_free(error,FALSE);
		destroy_dict(dict);
		return FALSE;
	} else {
		*err_str = NULL;
		g_string_free(error,TRUE);
		return TRUE;
	}
}

/*
 * We want to stop processing when we get to the end of the input.
 * (%option noyywrap is not used because if used then
 * some flex versions (eg: 2.5.35) generate code which causes
 * warnings by the Windows VC compiler).
 */

int yywrap(void) {
    return 1;
}
